#!/bin/bash
#==============================================================================================
#
# This file is licensed under the terms of the GNU General Public
# License version 2. This program is licensed "as is" without any
# warranty of any kind, whether express or implied.
#
# This file is a part of the Rebuild Armbian
# https://github.com/ophub/amlogic-s9xxx-armbian
#
# Description: Run on the x86_64 and aarch64 platforms (ubuntu/debian), Compile the kernel.
# Copyright (C) 2021~ https://www.kernel.org
# Copyright (C) 2021~ https://github.com/unifreq
# Copyright (C) 2021~ https://github.com/ophub/amlogic-s9xxx-armbian
#
# Command: sudo ./recompile
# Command optional parameters please refer to the source code repository
#
#======================================= Functions list =======================================
#
# error_msg          : Output error message
# mount_try          : Mount the image file, fail again
# log_to_file        : Log kernel compilation output to a file
#
# init_var           : Initialize all variables
# toolchain_check    : Check and install the cross-compilation toolchain
# query_version      : Query the latest kernel version
# apply_patch        : Apply custom kernel patches
# get_kernel_source  : Get the kernel source code
#
# chroot_armbian     : Chroot into Armbian to generate initrd.img, uInitrd and make scripts
# unmount_armbian    : Helper function that unmounts the armbian system
# compile_env        : Set up the compile kernel environment
# compile_dtbs       : Compile the dtbs
# compile_kernel     : Compile the kernel
# packit_dtbs        : Packit dtbs files
# packit_kernel      : Packit boot, modules and header files
# compile_selection  : Choose to compile dtbs or all kernels
# clean_tmp          : Clear temporary files
#
# loop_recompile     : Loop to compile kernel
#
#=============================== Set make environment variables ===============================
#
# Related file storage path
current_path="${PWD}"
compile_path="${current_path}/compile-kernel"
kernel_path="${compile_path}/kernel"
config_path="${compile_path}/tools/config"
script_path="${compile_path}/tools/script"
kernel_patch_path="${compile_path}/tools/patch"
armbian_path="${compile_path}/tools/armbian"
output_path="${compile_path}/output"
chroot_path="${output_path}/chroot"
chroot_file="${chroot_path}/chroot_armbian.img"
[[ -d "${kernel_path}" ]] || mkdir -p ${kernel_path}
[[ -d "${output_path}" ]] || mkdir -p ${output_path}

# Set the system file path to be used
arch_info="$(uname -m)"
host_id="$(cat /etc/os-release 2>/dev/null | grep '^ID=.*' | cut -d'=' -f2)"
host_release="$(cat /etc/os-release 2>/dev/null | grep '^VERSION_CODENAME=.*' | cut -d'=' -f2)"
host_version_id="$(cat /etc/os-release 2>/dev/null | grep '^VERSION_ID=.*' | cut -d'=' -f2 | tr -d '".')"
[[ -z "${host_version_id}" ]] && host_version_id="2404"

# Set the default for downloading kernel sources from github.com
repo_owner="unifreq"
repo_branch="main"
build_kernel=("6.1.y" "6.12.y")
all_kernel=("5.10.y" "5.15.y" "6.1.y" "6.6.y" "6.12.y")
# Set whether to use the latest kernel, options: [ true / false ]
auto_kernel="true"
# Set whether to apply custom kernel patches, options: [ true / false ]
auto_patch="false"
# Set custom signature for the kernel
custom_name="-ophub"
# Set the kernel compile object, options: [ dtbs / all ]
package_list="all"
# Set the compression format, options: [ gzip / lzma / xz / zstd ]
compress_format="xz"
# Set whether to clear ccache before compiling the kernel, options: [ true / false ]
ccache_clear="false"
# Set whether to automatically delete the source code after the kernel is compiled
delete_source="false"
# Set make log silent output, options: [ true / false ]
silent_log="false"
# Set whether to log compilation output to file, options: [ true / false ]
enable_log="false"
output_logfile="/var/log/kernel_compile_$(date +%Y-%m-%d_%H-%M-%S).log"

# Cross compile toolchain download mirror
dev_repo="https://github.com/ophub/kernel/releases/download/dev"
# Arm GNU Toolchain source: https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads
gun_file_x86_64="arm-gnu-toolchain-14.3.rel1-x86_64-aarch64-none-linux-gnu.tar.xz"
gun_file_aarch64="arm-gnu-toolchain-14.3.rel1-aarch64-aarch64-none-linux-gnu.tar.xz"
# Armenian file source: https://github.com/ophub/amlogic-s9xxx-armbian/releases
armbian_rootfs_file="armbian.tar.xz"
# Set the toolchain path
toolchain_path="/usr/local/toolchain"
# Set the default cross-compilation toolchain: [ clang / gcc / gcc-14.2, etc. ]
toolchain_name="gcc"
# QEMU BINARY
qemu_binary_arm64="qemu-aarch64-static"

# CCACHE Configuration
# Force specific cache directory (Override defaults)
export CCACHE_DIR="/root/.ccache"
# Rewrite absolute paths to relative (Enable Host/Docker sharing)
export CCACHE_BASEDIR="${compile_path}"
export CCACHE_NOHASHDIR="true"
# Check compiler by content hash, not modification time
export CCACHE_SLOPPINESS="time_macros,file_macro,include_file_ctime,include_file_mtime,system_headers,locale"
export CCACHE_COMPILERCHECK="content"

# Set font color
STEPS="[\033[95m STEPS \033[0m]"
INFO="[\033[94m INFO \033[0m]"
SUCCESS="[\033[92m SUCCESS \033[0m]"
WARNING="[\033[93m WARNING \033[0m]"
ERROR="[\033[91m ERROR \033[0m]"
#
#==============================================================================================

error_msg() {
    echo -e "${ERROR} ${1}"
    exit 1
}

log_to_file() {
    echo -e "${STEPS} Initializing kernel compilation log..."

    if touch "${output_logfile}" 2>/dev/null; then
        echo -e "${INFO} Kernel compilation log will be saved to: [ ${output_logfile} ]"
        exec &> >(tee -a "${output_logfile}")
    else
        echo -e "${WARNING} Failed to create log file [ ${output_logfile} ]. Logging to console only."
    fi
}

mount_try() {
    # Check mount parameters
    m_dev="${1}"
    m_target="${2}"
    [[ -n "${m_dev}" && -n "${m_target}" ]] || {
        error_msg "Mount parameter is missing: [ ${m_dev}, ${m_target} ]"
    }

    t="1"
    max_try="10"
    while [[ "${t}" -le "${max_try}" ]]; do
        mount ${m_dev} ${m_target}
        if [[ "${?}" -eq "0" ]]; then
            break
        else
            sync && sleep 3
            umount -f ${m_target} 2>/dev/null
            ((t++))
        fi
    done
    [[ "${t}" -gt "${max_try}" ]] && error_msg "[ ${t} ] attempts to mount failed."
}

init_var() {
    echo -e "${STEPS} Start Initializing Variables..."

    # If it is followed by [ : ], it means that the option requires a parameter value
    local options="k:a:n:m:p:r:t:c:d:s:z:l:"
    parsed_args=$(getopt -o "${options}" -- "${@}")
    [[ ${?} -ne 0 ]] && error_msg "Parameter parsing failed."
    eval set -- "${parsed_args}"

    while true; do
        case "${1}" in
        -k | --Kernel)
            if [[ -n "${2}" ]]; then
                if [[ "${2}" == "all" ]]; then
                    build_kernel=(${all_kernel[@]})
                else
                    oldIFS="${IFS}"
                    IFS="_"
                    build_kernel=(${2})
                    IFS="${oldIFS}"
                fi
                shift 2
            else
                error_msg "Invalid -k parameter [ ${2} ]!"
            fi
            ;;
        -a | --AutoKernel)
            if [[ -n "${2}" ]]; then
                auto_kernel="${2}"
                shift 2
            else
                error_msg "Invalid -a parameter [ ${2} ]!"
            fi
            ;;
        -n | --customName)
            if [[ -n "${2}" ]]; then
                custom_name="${2// /}"
                [[ "${custom_name:0:1}" != "-" ]] && custom_name="-${custom_name}"
                shift 2
            else
                error_msg "Invalid -n parameter [ ${2} ]!"
            fi
            ;;
        -m | --MakePackage)
            if [[ -n "${2}" ]]; then
                package_list="${2}"
                shift 2
            else
                error_msg "Invalid -m parameter [ ${2} ]!"
            fi
            ;;
        -p | --AutoPatch)
            if [[ -n "${2}" ]]; then
                auto_patch="${2}"
                shift 2
            else
                error_msg "Invalid -p parameter [ ${2} ]!"
            fi
            ;;
        -r | --Repository)
            if [[ -n "${2}" ]]; then
                repo_owner="${2}"
                shift 2
            else
                error_msg "Invalid -r parameter [ ${2} ]!"
            fi
            ;;
        -t | --Toolchain)
            if [[ -n "${2}" ]]; then
                toolchain_name="${2}"
                shift 2
            else
                error_msg "Invalid -t parameter [ ${2} ]!"
            fi
            ;;
        -z | --CompressFormat)
            if [[ -n "${2}" ]]; then
                compress_format="${2}"
                shift 2
            else
                error_msg "Invalid -z parameter [ ${2} ]!"
            fi
            ;;
        -d | --DeleteSource)
            if [[ -n "${2}" ]]; then
                delete_source="${2}"
                shift 2
            else
                error_msg "Invalid -d parameter [ ${2} ]!"
            fi
            ;;
        -s | --SilentLog)
            if [[ -n "${2}" ]]; then
                silent_log="${2}"
                shift 2
            else
                error_msg "Invalid -s parameter [ ${2} ]!"
            fi
            ;;
        -c | --CcacheClear)
            if [[ -n "${2}" ]]; then
                ccache_clear="${2}"
                shift 2
            else
                error_msg "Invalid -c parameter [ ${2} ]!"
            fi
            ;;
        -l | --EnableLog)
            if [[ -n "${2}" ]]; then
                enable_log="${2}"
                shift 2
            else
                error_msg "Invalid -l parameter [ ${2} ]!"
            fi
            ;;
        --)
            shift
            break
            ;;
        *)
            [[ -n "${1}" ]] && error_msg "Invalid option [ ${1} ]!"
            break
            ;;
        esac
    done

    # Receive the value entered by the [ -r ] parameter
    input_r_value="${repo_owner//https\:\/\/github\.com\//}"
    code_owner="$(echo "${input_r_value}" | awk -F '@' '{print $1}' | awk -F '/' '{print $1}')"
    code_repo="$(echo "${input_r_value}" | awk -F '@' '{print $1}' | awk -F '/' '{print $2}')"
    code_branch="$(echo "${input_r_value}" | awk -F '@' '{print $2}')"
    #
    [[ -n "${code_owner}" ]] || error_msg "The [ -r ] parameter is invalid."
    [[ -n "${code_branch}" ]] || code_branch="${repo_branch}"

    # Set the gcc version code
    [[ "${toolchain_name}" =~ ^gcc-[0-9]+.[0-9]+ ]] && {
        gcc_version_code="${toolchain_name#*-}"
        gun_file_x86_64="arm-gnu-toolchain-${gcc_version_code}.rel1-x86_64-aarch64-none-linux-gnu.tar.xz"
        gun_file_aarch64="arm-gnu-toolchain-${gcc_version_code}.rel1-aarch64-aarch64-none-linux-gnu.tar.xz"
    }

    # Set cross compilation parameters
    export SRC_ARCH="arm64"
    export LOCALVERSION="${custom_name}"
}

toolchain_check() {
    cd ${current_path}
    echo -e "${STEPS} Start checking the toolchain for compiling the kernel..."

    # Install dependencies for jammy
    apt-get -qq update
    if [[ "${arch_info}" == "x86_64" ]]; then
        apt-get -qq install -y $(cat compile-kernel/tools/script/ubuntu${host_version_id}-build-armbian-depends)
        systemctl restart systemd-binfmt
    else
        apt-get -qq install -y $(cat compile-kernel/tools/script/armbian-compile-kernel-depends)
    fi

    # Download armbian
    if [[ ! -s "${armbian_path}/${armbian_rootfs_file}" ]]; then
        echo -e "${INFO} Start downloading Armbian rootfs file [ ${armbian_rootfs_file} ]..."
        rm -rf ${armbian_path} && mkdir -p ${armbian_path}

        # Download Armbian rootfs file. If it fails, wait 1 minute and try again, try 10 times.
        for i in {1..10}; do
            curl -fsSL "${dev_repo}/${armbian_rootfs_file}" -o "${armbian_path}/${armbian_rootfs_file}"
            [[ "${?}" -eq "0" ]] && break || sleep 60
        done
        [[ -s "${armbian_path}/${armbian_rootfs_file}" ]] || error_msg "Armbian file download failed"
    fi

    # Set the default path
    path_os_variable="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin"

    # Download the cross-compilation toolchain: [ clang / gcc ]
    [[ -d "/etc/apt/sources.list.d" ]] || mkdir -p /etc/apt/sources.list.d
    if [[ "${toolchain_name}" == "clang" ]]; then
        # Install LLVM
        echo -e "${INFO} Start installing the LLVM toolchain..."
        apt-get -qq install -y lsb-release software-properties-common gnupg
        curl -fsSL https://apt.llvm.org/llvm.sh | bash -s all
        [[ "${?}" -eq "0" ]] || error_msg "LLVM installation failed."

        # Set cross compilation parameters
        export PATH="${path_os_variable}"
        export CROSS_COMPILE="aarch64-linux-gnu-"
        export CC="ccache clang"
        export LD="ld.lld"
        export MFLAGS=" LLVM=1 LLVM_IAS=1 "
    else
        # Choosing Arm GNU Toolchain based on the platform
        if [[ "${arch_info}" == "x86_64" ]]; then
            gun_file="${gun_file_x86_64}"
            gun_bin="aarch64-none-linux-gnu-"
        else
            gun_file="${gun_file_aarch64}"
            gun_bin="aarch64-none-linux-gnu-"
        fi

        # Download Arm GNU Toolchain
        [[ -d "${toolchain_path}" ]] || mkdir -p ${toolchain_path}
        if [[ ! -d "${toolchain_path}/${gun_file//.tar.xz/}/bin" ]]; then
            echo -e "${INFO} Start downloading the ARM GNU toolchain [ ${gun_file} ]..."

            # Download the ARM GNU toolchain file. If it fails, wait 1 minute and try again, try 10 times.
            for i in {1..10}; do
                curl -fsSL "${dev_repo}/${gun_file}" -o "${toolchain_path}/${gun_file}"
                [[ "${?}" -eq "0" ]] && break || sleep 60
            done
            [[ "${?}" -eq "0" ]] || error_msg "GNU toolchain file download failed."

            # Decompress the ARM GNU toolchain file
            tar -xJf ${toolchain_path}/${gun_file} -C ${toolchain_path}
            rm -f ${toolchain_path}/${gun_file}

            # List and check directory names, and change them all to lowercase
            for dir in $(ls ${toolchain_path}); do
                if [[ -d "${toolchain_path}/${dir}" && "${dir}" != "${dir,,}" ]]; then
                    mv -f ${toolchain_path}/${dir} ${toolchain_path}/${dir,,}
                fi
            done
            [[ -d "${toolchain_path}/${gun_file//.tar.xz/}/bin" ]] || error_msg "The gcc is not set!"
        fi

        # Add ${PATH} variable
        path_gcc="${toolchain_path}/${gun_file//.tar.xz/}/bin:${path_os_variable}"
        export PATH="${path_gcc}"

        # Set cross compilation parameters
        export CROSS_COMPILE="${toolchain_path}/${gun_file//.tar.xz/}/bin/${gun_bin}"
        export CC="ccache ${CROSS_COMPILE}gcc"
        export LD="${CROSS_COMPILE}ld.bfd"
        export MFLAGS=""
    fi

    # Setup ccache
    echo -e "${INFO} Setting up ccache..."
    ccache -M 10G 2>/dev/null
    ccache -z 2>/dev/null
}

query_version() {
    cd ${current_path}
    echo -e "${STEPS} Start querying the latest kernel version..."

    # Set empty array
    tmp_arr_kernels=()

    # Query the latest kernel in a loop
    i=1
    for KERNEL_VAR in "${build_kernel[@]}"; do
        echo -e "${INFO} (${i}) Auto query the latest kernel version of the same series for [ ${KERNEL_VAR} ]"
        # Identify the kernel mainline
        MAIN_LINE="$(echo ${KERNEL_VAR} | awk -F '.' '{print $1"."$2}')"

        if [[ -z "${code_repo}" ]]; then linux_repo="linux-${MAIN_LINE}.y"; else linux_repo="${code_repo}"; fi
        github_kernel_repo="${code_owner}/${linux_repo}/${code_branch}"
        github_kernel_ver="https://raw.githubusercontent.com/${github_kernel_repo}/Makefile"
        # latest_version="125"
        latest_version="$(curl -s ${github_kernel_ver} | grep -oE "SUBLEVEL =.*" | head -n 1 | grep -oE '[0-9]{1,3}')"
        if [[ "${?}" -eq "0" && -n "${latest_version}" ]]; then
            tmp_arr_kernels[${i}]="${MAIN_LINE}.${latest_version}"
        else
            error_msg "Failed to query the kernel version in [ github.com/${github_kernel_repo} ]"
        fi
        echo -e "${INFO} (${i}) [ ${tmp_arr_kernels[$i]} ] is github.com/${github_kernel_repo} latest kernel. \n"

        ((i++))
    done

    # Reset the kernel array to the latest kernel version
    build_kernel=(${tmp_arr_kernels[@]})
}

apply_patch() {
    cd ${current_path}
    echo -e "${STEPS} Start applying custom kernel patches..."

    # Apply the common kernel patches
    if [[ -d "${kernel_patch_path}/common-kernel-patches" ]]; then
        echo -e "${INFO} Copy common kernel patches..."
        rm -f ${kernel_path}/${local_kernel_path}/*.patch
        cp -vf ${kernel_patch_path}/common-kernel-patches/*.patch -t ${kernel_path}/${local_kernel_path}

        cd ${kernel_path}/${local_kernel_path}
        for file in *.patch; do
            echo -e "${INFO} Apply kernel patch file: [ ${file} ]"
            patch -p1 <"${file}"
        done
        rm -f *.patch
    else
        echo -e "${INFO} No common kernel patches, skipping."
    fi

    # Apply the dedicated kernel patches
    if [[ -d "${kernel_patch_path}/${local_kernel_path}" ]]; then
        echo -e "${INFO} Copy [ ${local_kernel_path} ] version dedicated kernel patches..."
        rm -f ${kernel_path}/${local_kernel_path}/*.patch
        cp -vf ${kernel_patch_path}/${local_kernel_path}/*.patch -t ${kernel_path}/${local_kernel_path}

        cd ${kernel_path}/${local_kernel_path}
        for file in *.patch; do
            echo -e "${INFO} Apply kernel patch file: [ ${file} ]"
            patch -p1 <"${file}"
        done
        rm -f *.patch
    else
        echo -e "${INFO} No [ ${local_kernel_path} ] version dedicated kernel patches, skipping."
    fi
}

get_kernel_source() {
    cd ${current_path}
    echo -e "${STEPS} Start downloading the kernel source code..."

    [[ -d "${kernel_path}" ]] || mkdir -p ${kernel_path}

    # Download the kernel source code from github.com
    if [[ ! -d "${kernel_path}/${local_kernel_path}" ]]; then
        echo -e "${INFO} Start cloning from [ https://github.com/${server_kernel_repo} -b ${code_branch} ]"

        # Clone the latest kernel source code. If it fails, wait 1 minute and try again, try 10 times.
        for i in {1..10}; do
            git clone -q --single-branch --depth=1 --branch=${code_branch} https://github.com/${server_kernel_repo} ${kernel_path}/${local_kernel_path}
            [[ "${?}" -eq "0" ]] && break || sleep 60
        done
        [[ "${?}" -eq "0" ]] || error_msg "[ https://github.com/${server_kernel_repo} ] Clone failed."
    else
        # Get a local kernel version
        local_makefile="${kernel_path}/${local_kernel_path}/Makefile"
        local_makefile_version="$(cat ${local_makefile} | grep -oE "VERSION =.*" | head -n 1 | grep -oE '[0-9]{1,3}')"
        local_makefile_patchlevel="$(cat ${local_makefile} | grep -oE "PATCHLEVEL =.*" | head -n 1 | grep -oE '[0-9]{1,3}')"
        local_makefile_sublevel="$(cat ${local_makefile} | grep -oE "SUBLEVEL =.*" | head -n 1 | grep -oE '[0-9]{1,3}')"

        # Local version and server version comparison
        if [[ "${auto_kernel}" =~ ^(true|yes)$ ]] && [[ "${kernel_sub}" -gt "${local_makefile_sublevel}" ]]; then
            # Pull the latest source code of the server
            cd ${kernel_path}/${local_kernel_path}
            git checkout ${code_branch} && git reset --hard origin/${code_branch} && git pull
            kernel_version="${local_makefile_version}.${local_makefile_patchlevel}.${kernel_sub}"
            echo -e "${INFO} Synchronize the upstream source code, compile the kernel version [ ${kernel_version} ]."
        else
            # Reset to local kernel version number
            kernel_version="${local_makefile_version}.${local_makefile_patchlevel}.${local_makefile_sublevel}"
            echo -e "${INFO} Use local source code, compile the kernel version [ ${kernel_version} ]."
        fi
    fi

    # Remove the local version number
    rm -f ${kernel_path}/${local_kernel_path}/localversion

    # Apply custom kernel patches
    [[ "${auto_patch}" =~ ^(true|yes)$ ]] && apply_patch
}

unmount_armbian() {
    echo -e "${STEPS} Unmounting Armbian chroot"
    tag_rootfs="${chroot_path}/root"
    cd ${current_path}

    sync && sleep 3
    umount -l ${tag_rootfs}/dev/pts 2>/dev/null
    umount -R -l ${tag_rootfs}/dev 2>/dev/null
    umount -l ${tag_rootfs}/run 2>/dev/null
    umount -l ${tag_rootfs}/sys 2>/dev/null
    umount -l ${tag_rootfs}/proc 2>/dev/null
    umount -l ${tag_rootfs}/boot 2>/dev/null
    umount -l ${tag_rootfs} 2>/dev/null
    losetup -D 2>/dev/null
}

chroot_armbian() {
    cd ${current_path}
    echo -e "${STEPS} Create chroot..."
    echo -e "${INFO} Current space usage: \n$(df -hT ${chroot_path}) \n"

    # Extract the armbian rootfs file
    rm -f ${chroot_file}
    tar -xJf ${armbian_path}/${armbian_rootfs_file} -O >"${chroot_file}"
    [[ "${?}" -eq "0" && -s "${chroot_file}" ]] || error_msg "Armbian rootfs file extraction failed."

    # Mount the armbian system
    tag_rootfs="${chroot_path}/root"

    loop_armbian="$(losetup -P -f --show "${chroot_file}")"
    [[ -n "${loop_armbian}" ]] || error_msg "losetup ${chroot_file} failed."

    mount_try ${loop_armbian}p2 ${tag_rootfs}
    mount_try ${loop_armbian}p1 ${tag_rootfs}/boot

    # Get Armbian PLATFORM value
    ARMBIAN_PLATFORM="$(cat ${tag_rootfs}/etc/ophub-release 2>/dev/null | grep -E "^PLATFORM=.*" | cut -d"'" -f2)"
    [[ -n "${ARMBIAN_PLATFORM}" ]] && echo -e "${INFO} Armbian PLATFORM: [ ${ARMBIAN_PLATFORM} ]"

    echo -e "${INFO} Copy the kernel files to the armbian system..."
    # Remove current system files for /boot
    rm -f ${tag_rootfs}/boot/{config-*,initrd.img-*,System.map-*,vmlinuz-*,uInitrd*,*Image}
    # Copy /boot related files into armbian system
    [[ -d "${tag_rootfs}/boot" ]] || mkdir -p ${tag_rootfs}/boot
    cp -f ${kernel_path}/${local_kernel_path}/System.map ${tag_rootfs}/boot/System.map-${kernel_outname}
    cp -f ${kernel_path}/${local_kernel_path}/.config ${tag_rootfs}/boot/config-${kernel_outname}
    cp -f ${kernel_path}/${local_kernel_path}/arch/${SRC_ARCH}/boot/Image ${tag_rootfs}/boot/vmlinuz-${kernel_outname}
    if [[ -z "${ARMBIAN_PLATFORM}" || "${ARMBIAN_PLATFORM}" =~ ^(rockchip|allwinner)$ ]]; then
        cp -f ${tag_rootfs}/boot/vmlinuz-${kernel_outname} ${tag_rootfs}/boot/Image
    else
        cp -f ${tag_rootfs}/boot/vmlinuz-${kernel_outname} ${tag_rootfs}/boot/zImage
    fi
    #echo -e "${INFO} Kernel copy results in the [ ${tag_rootfs}/boot ] directory: \n$(ls -l ${tag_rootfs}/boot) \n"

    # Remove current system files for /usr/lib/modules
    rm -rf ${tag_rootfs}/usr/lib/modules/*
    # Copy /usr/lib/modules related files into armbian system
    [[ -d "${tag_rootfs}/usr/lib/modules" ]] && mkdir -p ${tag_rootfs}/usr/lib/modules
    cp -rf ${output_path}/modules/lib/modules/${kernel_outname} -t ${tag_rootfs}/usr/lib/modules
    #echo -e "${INFO} Kernel copy results in the [ ${tag_rootfs}/usr/lib/modules ] directory: \n$(ls -l ${tag_rootfs}/usr/lib/modules) \n"

    cd ${kernel_path}/${local_kernel_path}

    echo -e "${INFO} Copy the kernel source tree to the armbian system..."
    # Set the kernel source tree path
    armbian_kernel_path="${tag_rootfs}/opt/linux-kernel"
    mkdir -p ${armbian_kernel_path}
    # Set the git configuration and copy the kernel source tree
    git config --global --add safe.directory ${PWD}
    git archive --format=tar ${code_branch} | tar xf - -C ${armbian_kernel_path}
    cp -af include/config "${armbian_kernel_path}/include"
    cp -af include/generated "${armbian_kernel_path}/include"
    cp -af arch/${SRC_ARCH}/include/generated "${armbian_kernel_path}/arch/${SRC_ARCH}/include"
    cp -af .config Module.symvers ${armbian_kernel_path}
    [[ "${?}" -eq "0" ]] || error_msg "Copy the kernel source tree to the armbian system failed."

    cd ${current_path}

    # COMPRESS: [ gzip | lzma | xz | zstd | lz4 ]
    [[ "${compress_format}" =~ ^(gzip|lzma|xz|zstd|lz4)$ ]] || {
        echo -e "${WARNING} The compression format [ ${compress_format} ] is invalid, reset to [ xz ] format."
        compress_format="xz"
    }
    compress_initrd_file="${tag_rootfs}/etc/initramfs-tools/initramfs.conf"
    if [[ -f "${compress_initrd_file}" ]]; then
        sed -i "s|^COMPRESS=.*|COMPRESS=${compress_format}|g" ${compress_initrd_file}
        compress_settings="$(cat ${compress_initrd_file} | grep -E ^COMPRESS=)"
        echo -e "${INFO} Set the [ ${compress_settings} ] in the initramfs.conf file."
    else
        error_msg "The [ ${compress_initrd_file} ] file does not exist."
    fi

    if [[ "${arch_info}" == "x86_64" ]]; then
        echo -e "${INFO} Copy the [ ${qemu_binary_arm64} ] file to the armbian system..."
        [[ -f "/usr/bin/${qemu_binary_arm64}" ]] && cp -f /usr/bin/${qemu_binary_arm64} ${tag_rootfs}/usr/bin/
        #echo -e "${INFO} The [ ${qemu_binary_arm64} ] file copy results: \n$(ls -l ${tag_rootfs}/usr/bin/${qemu_binary_arm64}) \n"
    fi

    # Copy the /etc/resolv.conf file to the armbian system
    [[ -f "/etc/resolv.conf" ]] && {
        echo -e "${INFO} Copy the [ /etc/resolv.conf ] file to the armbian system..."
        cp -f /etc/resolv.conf ${tag_rootfs}/etc/resolv.conf 2>/dev/null
    }

    echo -e "${INFO} Copy the [ ubuntu_chroot_armbian.sh ] script to the armbian system..."
    cp -f ${script_path}/ubuntu_chroot_armbian.sh ${tag_rootfs}/root
    chmod +x ${tag_rootfs}/root/ubuntu_chroot_armbian.sh
    #echo -e "${INFO} Kernel copy results in the [ ${tag_rootfs}/root ] directory: \n$(ls -l ${tag_rootfs}/root) \n"

    # Enter the armbian system to generate /boot/uInitrd-${kernel_outname} file
    echo -e "${STEPS} Start mounting the armbian system..."
    [[ -d "${tag_rootfs}/dev/pts" ]] || mkdir -p ${tag_rootfs}/dev/pts
    mount -t proc /proc ${tag_rootfs}/proc
    mount -t sysfs /sys ${tag_rootfs}/sys
    mount -t devpts devpts ${tag_rootfs}/dev/pts
    mount --bind /dev ${tag_rootfs}/dev
    mount --bind /run ${tag_rootfs}/run
    chmod 0666 ${tag_rootfs}/dev/null
    sync && sleep 3

    # Always unmount armbian on exit
    trap unmount_armbian SIGINT
    echo -e "${INFO} Entering the chroot environment, please wait..."
    chroot ${tag_rootfs} /bin/bash -c "/root/ubuntu_chroot_armbian.sh ${kernel_outname}"
    if [[ "${?}" -ne "0" || ! -f "${tag_rootfs}/boot/uInitrd-${kernel_outname}" ]]; then
        unmount_armbian
        error_msg "Create chroot uInitrd-${kernel_outname} file failed."
    fi
    cd ${current_path}

    echo -e "${STEPS} Start copying files from Armbian system..."

    # Copy the generated uInitrd file to the current system
    echo -e "${INFO} Copy the boot files from armbian [ /boot ]"
    cp -rf ${tag_rootfs}/boot/*${kernel_outname} ${output_path}/boot

    # Copy the generated header files to the current system
    echo -e "${INFO} Copy the header files from armbian [ /opt/header ]"
    cp -f ${tag_rootfs}/opt/header/header-${kernel_outname}.tar.gz ${output_path}/${kernel_version}

    trap - SIGINT
    # Unmount the armbian system
    unmount_armbian
}

compile_env() {
    cd ${current_path}
    echo -e "${STEPS} Start checking local compilation environments..."

    # Get kernel output name
    kernel_outname="${kernel_version}${custom_name}"
    echo -e "${INFO} Compile kernel output name: [ ${kernel_outname} ]"

    # Create a temp directory
    echo -e "${INFO} Create a temp directory: [ ${output_path} ]"
    rm -rf ${output_path}/{chroot/,boot/,dtb/,modules/,header/,${kernel_version}/}
    mkdir -p ${output_path}/{chroot/{root/boot/,},boot/,dtb/{allwinner/,amlogic/,rockchip/},modules/,header/,${kernel_version}/}

    cd ${kernel_path}/${local_kernel_path}
    echo -e "${STEPS} Set cross compilation parameters..."

    # Show variable
    echo -e "${INFO} ARCH: [ ${SRC_ARCH} ]"
    echo -e "${INFO} LOCALVERSION: [ ${LOCALVERSION} ]"
    echo -e "${INFO} CROSS_COMPILE: [ ${CROSS_COMPILE} ]"
    echo -e "${INFO} CC: [ ${CC} ]"
    echo -e "${INFO} LD: [ ${LD} ]"

    # Set generic make string
    MAKE_SET_STRING=" ARCH=${SRC_ARCH} CROSS_COMPILE=${CROSS_COMPILE} ${MFLAGS} LOCALVERSION=${LOCALVERSION} "

    # Make clean/mrproper
    make ${MAKE_SET_STRING} CC="${CC}" LD="${LD}" mrproper

    # Clear ccache if enabled
    [[ "${ccache_clear}" =~ ^(true|yes)$ ]] && {
        echo -e "${INFO} Clear ccache before compiling the kernel..."
        ccache -C 2>/dev/null
    }

    # Check .config file
    if [[ ! -s ".config" ]]; then
        [[ -s "${config_path}/config-${kernel_verpatch}" ]] || error_msg "Missing [ config-${kernel_verpatch} ] template!"
        echo -e "${INFO} Copy [ ${config_path}/config-${kernel_verpatch} ] to [ .config ]"
        cp -f ${config_path}/config-${kernel_verpatch} .config
    else
        echo -e "${INFO} Use the .config file in the current directory."
    fi
    # Clear kernel signature
    sed -i "s|CONFIG_LOCALVERSION=.*|CONFIG_LOCALVERSION=\"\"|" .config

    # Enable/Disabled Linux Kernel Clang LTO
    [[ "${toolchain_name}" == "clang" ]] && {
        kernel_x="$(echo "${kernel_version}" | cut -d '.' -f1)"
        kernel_y="$(echo "${kernel_version}" | cut -d '.' -f2)"
        if [[ "${kernel_x}" -ge "6" ]] || [[ "${kernel_x}" -eq "5" && "${kernel_y}" -ge "12" ]]; then
            scripts/config -e LTO_CLANG_THIN
        else
            scripts/config -d LTO_CLANG_THIN
        fi

        # Add RUST support for version 6.1.y and later versions
        if [[ "${kernel_x}" -gt 6 ]] || [[ "${kernel_x}" -eq 6 && "${kernel_y}" -ge 1 ]]; then
            echo -e "${INFO} Kernel version [ ${kernel_version} ] requires RUST. Preparing the environment..."
            curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
            export PATH="${HOME}/.cargo/bin:${PATH}"

            echo -e "${INFO} Setting Rust toolchain to version required by the kernel..."
            rustup override set $(scripts/min-tool-version.sh rustc)

            echo -e "${INFO} Adding rust-src component..."
            rustup component add rust-src

            echo -e "${INFO} Installing correct bindgen version..."
            cargo uninstall bindgen-cli bindgen >/dev/null 2>&1 || true
            BINDGEN_VERSION="$(scripts/min-tool-version.sh bindgen 2>/dev/null || echo "0.65.1")"
            cargo install --locked --version "${BINDGEN_VERSION}" bindgen-cli 2>/dev/null || cargo install --locked --version "${BINDGEN_VERSION}" bindgen

            echo -e "${INFO} Rust environment is ready. Enabling RUST support in kernel config..."
            scripts/config -e RUST
            scripts/config -e RUST_IS_AVAILABLE
        else
            echo -e "${INFO} Skip Rust environment configuration for kernel version [ ${kernel_version} ]"
        fi
    }

    # Make menuconfig
    #make ${MAKE_SET_STRING} CC="${CC}" LD="${LD}" menuconfig

    # Set max process
    PROCESS="$(($(nproc 2>/dev/null || echo 2) - 1))"
    [[ -z "${PROCESS}" || "${PROCESS}" -lt "1" ]] && PROCESS="1" && echo "PROCESS: 1"
}

compile_dtbs() {
    cd ${kernel_path}/${local_kernel_path}

    # Make dtbs
    echo -e "${STEPS} Start compilation dtbs [ ${local_kernel_path} ]..."
    make ${MAKE_SET_STRING} CC="${CC}" LD="${LD}" dtbs -j${PROCESS}
    [[ "${?}" -eq "0" ]] && echo -e "${SUCCESS} The dtbs is compiled successfully."
}

compile_kernel() {
    cd ${kernel_path}/${local_kernel_path}

    # Set the make log silent output
    [[ "${silent_log}" =~ ^(true|yes)$ ]] && silent_print="-s" || silent_print=""

    # Make kernel
    echo -e "${STEPS} Start compilation kernel [ ${local_kernel_path} ]..."
    make ${silent_print} ${MAKE_SET_STRING} CC="${CC}" LD="${LD}" Image modules dtbs -j${PROCESS}
    [[ "${?}" -eq "0" ]] && echo -e "${SUCCESS} The kernel is compiled successfully."

    # Install modules
    echo -e "${STEPS} Install modules..."
    make ${silent_print} ${MAKE_SET_STRING} CC="${CC}" LD="${LD}" INSTALL_MOD_PATH=${output_path}/modules modules_install
    [[ "${?}" -eq "0" ]] && echo -e "${SUCCESS} The modules is installed successfully."

    # Strip debug information
    STRIP="${CROSS_COMPILE}strip"
    find ${output_path}/modules -name "*.ko" -print0 | xargs -0 ${STRIP} --strip-debug 2>/dev/null
    [[ "${?}" -eq "0" ]] && echo -e "${SUCCESS} The modules is stripped successfully."

    # Chroot into Armbian to generate initrd.img, uInitrd and make scripts
    chroot_armbian
}

packit_dtbs() {
    # Pack 3 dtbs files
    echo -e "${STEPS} Packing the [ ${kernel_outname} ] dtbs packages..."

    cd ${output_path}/dtb/allwinner
    cp -f ${kernel_path}/${local_kernel_path}/arch/${SRC_ARCH}/boot/dts/allwinner/*.dtb . 2>/dev/null
    [[ "${?}" -eq "0" ]] && {
        [[ -d "${kernel_path}/${local_kernel_path}/arch/${SRC_ARCH}/boot/dts/allwinner/overlay" ]] && {
            mkdir -p overlay
            cp -f ${kernel_path}/${local_kernel_path}/arch/${SRC_ARCH}/boot/dts/allwinner/overlay/*.dtbo overlay/ 2>/dev/null
        }
        tar -czf dtb-allwinner-${kernel_outname}.tar.gz *
        mv -f *.tar.gz ${output_path}/${kernel_version}
        echo -e "${SUCCESS} The [ dtb-allwinner-${kernel_outname}.tar.gz ] file is packaged."
    }

    cd ${output_path}/dtb/amlogic
    cp -f ${kernel_path}/${local_kernel_path}/arch/${SRC_ARCH}/boot/dts/amlogic/*.dtb . 2>/dev/null
    [[ "${?}" -eq "0" ]] && {
        [[ -d "${kernel_path}/${local_kernel_path}/arch/${SRC_ARCH}/boot/dts/amlogic/overlay" ]] && {
            mkdir -p overlay
            cp -f ${kernel_path}/${local_kernel_path}/arch/${SRC_ARCH}/boot/dts/amlogic/overlay/*.dtbo overlay/ 2>/dev/null
        }
        tar -czf dtb-amlogic-${kernel_outname}.tar.gz *
        mv -f *.tar.gz ${output_path}/${kernel_version}
        echo -e "${SUCCESS} The [ dtb-amlogic-${kernel_outname}.tar.gz ] file is packaged."
    }

    cd ${output_path}/dtb/rockchip
    cp -f ${kernel_path}/${local_kernel_path}/arch/${SRC_ARCH}/boot/dts/rockchip/*.dtb . 2>/dev/null
    [[ "${?}" -eq "0" ]] && {
        [[ -d "${kernel_path}/${local_kernel_path}/arch/${SRC_ARCH}/boot/dts/rockchip/overlay" ]] && {
            mkdir -p overlay
            cp -f ${kernel_path}/${local_kernel_path}/arch/${SRC_ARCH}/boot/dts/rockchip/overlay/*.dtbo overlay/ 2>/dev/null
        }
        tar -czf dtb-rockchip-${kernel_outname}.tar.gz *
        mv -f *.tar.gz ${output_path}/${kernel_version}
        echo -e "${SUCCESS} The [ dtb-rockchip-${kernel_outname}.tar.gz ] file is packaged."
    }
}

packit_kernel() {
    # Pack 3 kernel files
    echo -e "${STEPS} Packing the [ ${kernel_outname} ] boot, modules and header packages..."

    cd ${output_path}/boot
    rm -rf dtb-*
    chmod +x *
    tar -czf boot-${kernel_outname}.tar.gz *
    mv -f *.tar.gz ${output_path}/${kernel_version}
    echo -e "${SUCCESS} The [ boot-${kernel_outname}.tar.gz ] file is packaged."

    cd ${output_path}/modules/lib/modules
    tar -czf modules-${kernel_outname}.tar.gz *
    mv -f *.tar.gz ${output_path}/${kernel_version}
    echo -e "${SUCCESS} The [ modules-${kernel_outname}.tar.gz ] file is packaged."

    cd ${output_path}/${kernel_version}
    [[ -n "$(ls header-${kernel_outname}.tar.gz 2>/dev/null)" ]] && {
        echo -e "${SUCCESS} The [ header-${kernel_outname}.tar.gz ] file is packaged."
    }
}

compile_selection() {
    # Compile by selection
    if [[ "${package_list}" == "dtbs" ]]; then
        compile_dtbs
        packit_dtbs
    else
        compile_kernel
        packit_dtbs
        packit_kernel
    fi

    # Add sha256sum integrity verification file
    cd ${output_path}/${kernel_version}
    sha256sum * >sha256sums
    echo -e "${SUCCESS} The [ sha256sums ] file has been generated"

    cd ${output_path}
    tar -czf ${kernel_version}.tar.gz ${kernel_version}

    echo -e "${INFO} Kernel series files are stored in [ ${output_path} ]."
}

clean_tmp() {
    cd ${current_path}
    echo -e "${STEPS} Clear the space..."

    sync && sleep 3
    rm -rf ${output_path}/{chroot/,boot/,dtb/,modules/,header/,${kernel_version}/}
    [[ "${delete_source}" =~ ^(true|yes)$ ]] && rm -rf ${kernel_path}/* 2>/dev/null

    # Show ccache statistics
    echo -e "${INFO} ccache statistics:"
    ccache -s 2>/dev/null

    echo -e "${SUCCESS} All processes have been completed."
}

loop_recompile() {
    cd ${current_path}

    j="1"
    for k in "${build_kernel[@]}"; do
        # kernel_version, such as [ 6.1.15 ]
        kernel_version="${k}"
        # kernel <VERSION> and <PATCHLEVEL>, such as [ 6.1 ]
        kernel_verpatch="$(echo ${kernel_version} | awk -F '.' '{print $1"."$2}')"
        # kernel <SUBLEVEL>, such as [ 15 ]
        kernel_sub="$(echo ${kernel_version} | awk -F '.' '{print $3}')"

        # The loop variable assignment
        if [[ -z "${code_repo}" ]]; then
            server_kernel_repo="${code_owner}/linux-${kernel_verpatch}.y"
            local_kernel_path="linux-${kernel_verpatch}.y"
        else
            server_kernel_repo="${code_owner}/${code_repo}"
            local_kernel_path="${code_repo}-${code_branch}"
        fi

        # Show server start information
        echo -e "${INFO} Server space usage before starting to compile: \n$(df -hT ${kernel_path}) \n"

        # Check disk space size
        echo -ne "(${j}) Start compiling the kernel [\033[92m ${kernel_version} \033[0m]. "
        now_remaining_space="$(df -Tk ${kernel_path} | tail -n1 | awk '{print $5}' | echo $(($(xargs) / 1024 / 1024)))"
        if [[ "${now_remaining_space}" -le "15" ]]; then
            echo -e "${WARNING} Remaining space is less than 15G, exit the compilation."
            break
        else
            echo "Remaining space is ${now_remaining_space}G."
        fi

        # Execute the following functions in sequence
        get_kernel_source
        compile_env
        compile_selection
        clean_tmp

        ((j++))
    done
}

# Show welcome message
echo -e "${STEPS} Welcome to compile kernel! \n"
echo -e "${INFO} Server running on ${host_id}: [ Release: ${host_release} / Host: ${arch_info} ] \n"
# Check script permission, supports running on the x86_64 and aarch64 platforms (ubuntu/debian)
[[ "$(id -u)" == "0" ]] || error_msg "Please run this script as root: [ sudo ./${0} ]"
[[ "x86_64 aarch64" == *"${arch_info}"* ]] || error_msg "This script is only supported on the x86_64 and aarch64 platforms."

# Initialize variables
init_var "${@}"
# Output log to file
[[ "${enable_log}" =~ ^(true|yes)$ ]] && log_to_file
# Check and install the toolchain
toolchain_check
# Query the latest kernel version
[[ "${auto_kernel}" =~ ^(true|yes)$ ]] && query_version

# Show compile settings
echo -e "${INFO} Kernel compilation toolchain: [ ${toolchain_name} ]"
echo -e "${INFO} Kernel from: [ ${code_owner} ]"
echo -e "${INFO} Kernel patch: [ ${auto_patch} ]"
echo -e "${INFO} Kernel arch: [ ${SRC_ARCH} ]"
echo -e "${INFO} Kernel Package: [ ${package_list} ]"
echo -e "${INFO} kernel signature: [ ${custom_name} ]"
echo -e "${INFO} Latest kernel version: [ ${auto_kernel} ]"
echo -e "${INFO} kernel initrd compress: [ ${compress_format} ]"
echo -e "${INFO} Delete source: [ ${delete_source} ]"
echo -e "${INFO} Silent log: [ ${silent_log} ]"
echo -e "${INFO} Kernel List: [ $(echo ${build_kernel[@]} | xargs) ] \n"

# Loop to compile the kernel
loop_recompile

# Show server end information
echo -e "${STEPS} Server space usage after compilation: \n$(df -hT ${kernel_path}) \n"
echo -e "${SUCCESS} All process completed successfully."
